home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / js / calDavCalendar.js < prev    next >
Text File  |  2008-03-18  |  73KB  |  1,882 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Oracle Corporation code.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  *  Oracle Corporation
  18.  * Portions created by the Initial Developer are Copyright (C) 2004
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  23.  *   Dan Mosedale <dan.mosedale@oracle.com>
  24.  *   Mike Shaver <mike.x.shaver@oracle.com>
  25.  *   Gary van der Merwe <garyvdm@gmail.com>
  26.  *   Bruno Browning <browning@uwalumni.com>
  27.  *   Matthew Willis <lilmatt@mozilla.com>
  28.  *   Daniel Boelzle <daniel.boelzle@sun.com>
  29.  *   Philipp Kewisch <mozilla@kewis.ch>
  30.  *
  31.  * Alternatively, the contents of this file may be used under the terms of
  32.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  33.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  34.  * in which case the provisions of the GPL or the LGPL are applicable instead
  35.  * of those above. If you wish to allow use of your version of this file only
  36.  * under the terms of either the GPL or the LGPL, and not to allow others to
  37.  * use your version of this file under the terms of the MPL, indicate your
  38.  * decision by deleting the provisions above and replace them with the notice
  39.  * and other provisions required by the GPL or the LGPL. If you do not delete
  40.  * the provisions above, a recipient may use your version of this file under
  41.  * the terms of any one of the MPL, the GPL or the LGPL.
  42.  *
  43.  * ***** END LICENSE BLOCK ***** */
  44.  
  45. //
  46. // calDavCalendar.js
  47. //
  48.  
  49. // XXXdmose deal with generation goop
  50.  
  51. // XXXdmose deal with locking
  52.  
  53. // XXXdmose need to make and use better error reporting interface for webdav
  54. // (all uses of aStatusCode, probably)
  55.  
  56. // XXXdmose use real calendar result codes, not NS_ERROR_FAILURE for everything
  57.  
  58. const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>\n';
  59.  
  60. function calDavCalendar() {
  61.     this.initProviderBase();
  62.     this.unmappedProperties = [];
  63.     this.mUriParams = null;
  64.     this.mItemInfoCache = {};
  65.     this.mDisabled = false;
  66.     this.mCalHomeSet = null;
  67.     this.mPrincipalsNS = null;
  68.     this.mInBoxUrl = null;
  69.     this.mOutBoxUrl = null;
  70.     this.mHaveScheduling = false;
  71.     this.mMailToUrl = null;
  72.     this.mHrefIndex = [];
  73.     this.mAuthScheme = null;
  74.     this.mAuthRealm = null;
  75.     this.mObserver = null;
  76. }
  77.  
  78. // some shorthand
  79. const nsIWebDAVOperationListener =
  80.     Components.interfaces.nsIWebDAVOperationListener;
  81. const calICalendar = Components.interfaces.calICalendar;
  82. const nsISupportsCString = Components.interfaces.nsISupportsCString;
  83. const calIErrors = Components.interfaces.calIErrors;
  84. const calIFreeBusyInterval = Components.interfaces.calIFreeBusyInterval;
  85. const calICalDavCalendar = Components.interfaces.calICalDavCalendar;
  86. const calIDateTime = Components.interfaces.calIDateTime;
  87.  
  88. var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
  89.                         .getService(Components.interfaces.nsIXULAppInfo);
  90. var isOnBranch = appInfo.platformVersion.indexOf("1.8") == 0;
  91.  
  92.  
  93. function getLocationPath(item) {
  94.  
  95.     var locPath = this.mItemInfoCache[item.id].locationPath;
  96.     if (locPath) {
  97.         LOG("using locationPath: " + locPath);
  98.     } else {
  99.         locPath = item.id;
  100.         if (locPath) {
  101.             locPath += ".ics";
  102.         }
  103.         LOG("using locationPath: " + locPath);
  104.     }
  105.     return locPath;
  106. }
  107.  
  108. // END_OF_TIME needs to be the max value a PRTime can be
  109. const START_OF_TIME = -0x7fffffffffffffff;
  110. const END_OF_TIME = 0x7fffffffffffffff;
  111.  
  112. // used in checking calendar URI for (Cal)DAV-ness
  113. const kDavResourceTypeNone = 0;
  114. const kDavResourceTypeCollection = 1;
  115. const kDavResourceTypeCalendar = 2;
  116.  
  117. // used for etag checking
  118. const CALDAV_ADOPT_ITEM = 1;
  119. const CALDAV_MODIFY_ITEM = 2;
  120. const CALDAV_DELETE_ITEM = 3;
  121.  
  122. calDavCalendar.prototype = {
  123.     __proto__: calProviderBase.prototype,
  124.     //
  125.     // nsISupports interface
  126.     //
  127.     QueryInterface: function (aIID) {
  128.         return doQueryInterface(this, calDavCalendar.prototype, aIID,
  129.                                 [Components.interfaces.calICalendarProvider,
  130.                                  Components.interfaces.nsIInterfaceRequestor,
  131.                                  Components.interfaces.calIFreeBusyProvider,
  132.                                  calICalDavCalendar]);
  133.     },
  134.  
  135.     initMemoryCalendar: function caldav_iMC() {
  136.         this.mMemoryCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=memory"]
  137.                                          .createInstance(Components.interfaces.calICalendar);
  138.  
  139.         this.mMemoryCalendar.superCalendar = this;
  140.         this.mObserver = new calDavObserver(this);
  141.         this.mMemoryCalendar.addObserver(this.mObserver);
  142.         this.mMemoryCalendar.setProperty("relaxedMode", true);
  143.     },
  144.  
  145.     //
  146.     // calICalendarProvider interface
  147.     //
  148.     get prefChromeOverlay() {
  149.         return null;
  150.     },
  151.  
  152.     get displayName() {
  153.         return calGetString("calendar", "caldavName");
  154.     },
  155.  
  156.     createCalendar: function caldav_createCal() {
  157.         throw NS_ERROR_NOT_IMPLEMENTED;
  158.     },
  159.  
  160.     deleteCalendar: function caldav_deleteCal(cal, listener) {
  161.         throw NS_ERROR_NOT_IMPLEMENTED;
  162.     },
  163.  
  164.     //
  165.     // calICalendar interface
  166.     //
  167.  
  168.     // readonly attribute AUTF8String type;
  169.     get type() { return "caldav"; },
  170.  
  171.     mDisabled: false,
  172.  
  173.     mPrincipalsNS: null,
  174.  
  175.     mHaveScheduling: false,
  176.  
  177.     mMailToUrl: null,
  178.  
  179.     get canRefresh() {
  180.         return true;
  181.     },
  182.  
  183.     // mUriParams stores trailing ?parameters from the
  184.     // supplied calendar URI. Needed for (at least) Cosmo
  185.     // tickets
  186.     mUriParams: null,
  187.  
  188.     get uri() { return this.mUri },
  189.  
  190.     set uri(aUri) {
  191.         this.mUri = aUri;
  192.         this.initMemoryCalendar();
  193.  
  194.         this.checkDavResourceType();
  195.         return aUri;
  196.     },
  197.  
  198.     get mCalendarUri() {
  199.         calUri = this.mUri.clone();
  200.         var parts = calUri.spec.split('?');
  201.         if (parts.length > 1) {
  202.             calUri.spec = parts.shift();
  203.             this.mUriParams = '?' + parts.join('?');
  204.         }
  205.         if (calUri.spec.charAt(calUri.spec.length-1) != '/') {
  206.             calUri.spec += "/";
  207.         }
  208.         return calUri;
  209.     },
  210.  
  211.     setCalHomeSet: function caldav_setCalHomeSet() {
  212.         var calUri = this.mUri.clone();
  213.         var split1 = calUri.spec.split('?');
  214.         var baseUrl = split1[0];
  215.         if (baseUrl.charAt(baseUrl.length-1) == '/') {
  216.             baseUrl = baseUrl.substring(0, baseUrl.length-2);
  217.         }
  218.         var split2 = baseUrl.split('/');
  219.         split2.pop();
  220.         calUri.spec = split2.join('/') + '/';
  221.         this.mCalHomeSet = calUri;
  222.     },
  223.  
  224.     mOutBoxUrl:  null,
  225.  
  226.     mInBoxUrl: null,
  227.  
  228.     mAuthScheme: null,
  229.  
  230.     mAuthRealm: null,
  231.  
  232.     get authRealm() {
  233.         return this.mAuthRealm;
  234.     },
  235.  
  236.     makeUri: function caldav_makeUri(aInsertString) {
  237.         var spec = this.mCalendarUri.spec + aInsertString;
  238.         if (this.mUriParams) {
  239.             return spec + this.mUriParams;
  240.         }
  241.         return spec;
  242.     },
  243.  
  244.     get mLocationPath() {
  245.         return decodeURIComponent(this.mCalendarUri.path);
  246.     },
  247.  
  248.     // XXX todo: in general we want to do CalDAV scheduling, but for servers
  249.     //           that don't support it, we want Itip
  250.     // sendItipInvitations is now used from calProviderBase.
  251.  
  252.     promptOverwrite: function caldavPO(aMethod, aItem, aListener, aOldItem) {
  253.         var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
  254.                             getService(Components.interfaces.nsIPromptService);
  255.  
  256.         var promptTitle = calGetString("calendar", "itemModifiedOnServerTitle");
  257.         var promptMessage = calGetString("calendar", "itemModifiedOnServer");
  258.         var buttonLabel1;
  259.  
  260.         if (aMethod == CALDAV_MODIFY_ITEM) {
  261.             promptMessage += calGetString("calendar", "modifyWillLoseData");
  262.             buttonLabel1 = calGetString("calendar", "proceedModify");
  263.         } else {
  264.             promptMessage += calGetString("calendar", "deleteWillLoseData");
  265.             buttonLabel1 = calGetString("calendar", "proceedDelete");
  266.         }
  267.  
  268.         var buttonLabel2 = calGetString("calendar", "updateFromServer");
  269.  
  270.         var flags = promptService.BUTTON_TITLE_IS_STRING *
  271.                     promptService.BUTTON_POS_0 +
  272.                     promptService.BUTTON_TITLE_IS_STRING *
  273.                     promptService.BUTTON_POS_1;
  274.  
  275.         var choice = promptService.confirmEx(null, promptTitle, promptMessage,
  276.                                              flags, buttonLabel1, buttonLabel2,
  277.                                              null, null, {});
  278.  
  279.         if (choice == 0) {
  280.             if (aMethod == CALDAV_MODIFY_ITEM) {
  281.                 this.doModifyItem(aItem, aOldItem, aListener, true);
  282.             } else {
  283.                 this.doDeleteItem(aItem, aListener, true);
  284.             }
  285.         } else {
  286.             this.getUpdatedItem(aItem, aListener);
  287.         }
  288.  
  289.     },
  290.  
  291.     mItemInfoCache: null,
  292.  
  293.     mHrefIndex: null,
  294.  
  295.     /**
  296.      * prepare channel with standard request headers
  297.      * and upload data/content-type if needed
  298.      *
  299.      * @param arUri         channel Uri
  300.      * @param aUploadData   data to be uploaded, if any
  301.      * @param aContentType  value for Content-Type header, if any
  302.      */
  303.  
  304.     prepChannel: function caldavPC(aUri, aUploadData, aContentType) {
  305.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  306.                           .getService(Components.interfaces.nsIIOService);
  307.         var channel = ioService.newChannelFromURI(aUri);
  308.  
  309.         var httpchannel = channel.QueryInterface(Components.interfaces
  310.                                                            .nsIHttpChannel);
  311.  
  312.         httpchannel.setRequestHeader("Accept", "text/xml", false);
  313.         httpchannel.setRequestHeader("Accept-Charset", "utf-8,*;q=0.1", false);
  314.         httpchannel.notificationCallbacks = this;
  315.  
  316.         if (aUploadData) {
  317.             httpchannel = httpchannel.QueryInterface(Components.interfaces.
  318.                                                      nsIUploadChannel);
  319.             var converter =
  320.                 Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
  321.                           .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
  322.             converter.charset = "UTF-8";
  323.             var stream = converter.convertToInputStream(aUploadData);
  324.             httpchannel.setUploadStream(stream, aContentType, -1);
  325.         }
  326.         return httpchannel;
  327.     },
  328.  
  329.     /**
  330.      * addItem(); required by calICalendar.idl
  331.      * we actually use doAdoptItem()
  332.      *
  333.      * @param aItem       item to add
  334.      * @param aListener   listener for method completion
  335.      */
  336.  
  337.     addItem: function caldavAI(aItem, aListener) {
  338.         var newItem = aItem.clone();
  339.         return this.doAdoptItem(newItem, aListener, false);
  340.     },
  341.  
  342.     /**
  343.      * adooptItem(); required by calICalendar.idl
  344.      * we actually use doAdoptItem()
  345.      *
  346.      * @param aItem       item to check
  347.      * @param aListener   listener for method completion
  348.      */
  349.     adoptItem: function caldavAtI(aItem, aListener) {
  350.         var newItem = aItem.clone();
  351.         return this.doAdoptItem(newItem, aListener, false);
  352.     },
  353.  
  354.     /**
  355.      * Performs the actual addition of the item to CalDAV store
  356.      *
  357.      * @param aItem       item to add
  358.      * @param aListener   listener for method completion
  359.      * @param aIgnoreEtag ignore item etag
  360.      */
  361.     doAdoptItem: function caldavaDAI(aItem, aListener, aIgnoreEtag) {
  362.         if (aItem.id == null && aItem.isMutable) {
  363.             aItem.id = getUUID();
  364.         }
  365.  
  366.         if (aItem.id == null) {
  367.             if (aListener)
  368.                 aListener.onOperationComplete (this.superCalendar,
  369.                                                Components.results.NS_ERROR_FAILURE,
  370.                                                aListener.ADD,
  371.                                                aItem.id,
  372.                                                "Can't set ID on non-mutable item to addItem");
  373.             return;
  374.         }
  375.  
  376.         var locationPath = aItem.id + ".ics";
  377.         var itemUri = this.mCalendarUri.clone();
  378.         itemUri.spec = this.makeUri(locationPath);
  379.         LOG("itemUri.spec = " + itemUri.spec);
  380.  
  381.         var addListener = {};
  382.         var thisCalendar = this;
  383.         addListener.onStreamComplete =
  384.             function onPutComplete(aLoader, aContext, aStatus, aResultLength,
  385.                                    aResult) {
  386.             var status = aContext.responseStatus;
  387.             // 201 = HTTP "Created"
  388.             //
  389.             if (status == 201) {
  390.                 LOG("Item added successfully");
  391.  
  392.                 var retVal = Components.results.NS_OK;
  393.                 // Some CalDAV servers will modify items on PUT (add X-props,
  394.                 // change location, etc) so we'd best re-fetch in order to know
  395.                 // the current state of the item
  396.                 // Observers will be notified in getUpdatedItem()
  397.                 thisCalendar.getUpdatedItem(aItem, aListener);
  398.  
  399.             } else if (status == 200) {
  400.                 LOG("CalDAV: 200 received from server: server malfunction");
  401.                 retVal = Components.results.NS_ERROR_FAILURE;
  402.             } else if (status == 412) {
  403.                 LOG("CalDAV: etag exists on adopt item: server malfunction");
  404.                 retVal = Components.results.NS_ERROR_FAILURE;
  405.             } else {
  406.                 if (status > 999) {
  407.                     status = "0x" + aStatusCode.toString(16);
  408.                 }
  409.  
  410.                 // XXX real error handling
  411.                 LOG("Error adding item: " + status);
  412.                 retVal = Components.results.NS_ERROR_FAILURE;
  413.             }
  414.         }
  415.  
  416.         aItem.calendar = this.superCalendar;
  417.         aItem.generation = 1;
  418.  
  419.         // LOG("icalString = " + aItem.icalString);
  420.  
  421.         var httpchannel = this.prepChannel(itemUri, aItem.icalString,
  422.                                            "text/calendar; charset=utf-8");
  423.  
  424.  
  425.         if (!aIgnoreEtag) {
  426.             httpchannel.setRequestHeader("If-None-Match", "*", false);
  427.         }
  428.  
  429.         var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
  430.                              .createInstance(Components.interfaces
  431.                              .nsIStreamLoader);
  432.  
  433.         if (isOnBranch) {
  434.             streamLoader.init(httpchannel, addListener, httpchannel);
  435.         } else {
  436.             streamLoader.init(addListener);
  437.             httpchannel.asyncOpen(streamLoader, httpchannel);
  438.         }
  439.  
  440.         return;
  441.     },
  442.  
  443.     /**
  444.      * modifyItem(); required by calICalendar.idl
  445.      * we actually use doModifyItem()
  446.      *
  447.      * @param aItem       item to check
  448.      * @param aListener   listener for method completion
  449.      */
  450.     modifyItem: function caldavMI(aNewItem, aOldItem, aListener) {
  451.         return this.doModifyItem(aNewItem, aOldItem, aListener, false);
  452.     },
  453.  
  454.     /**
  455.      * Modifies existing item in CalDAV store.
  456.      *
  457.      * @param aItem       item to check
  458.      * @param aOldItem    previous version of item to be modified
  459.      * @param aListener   listener from original request
  460.      * @param aIgnoreEtag ignore item etag
  461.      */
  462.     doModifyItem: function caldavMI(aNewItem, aOldItem, aListener, aIgnoreEtag) {
  463.  
  464.         if (aNewItem.id == null) {
  465.  
  466.             // XXXYYY fix to match iface spec
  467.             // this is definitely an error
  468.             if (aListener) {
  469.                 try {
  470.                     aListener.onOperationComplete(this.superCalendar,
  471.                                                   Components.results.NS_ERROR_FAILURE,
  472.                                                   aListener.MODIFY,
  473.                                                   aItem.id,
  474.                                                   "ID for modifyItem doesn't exist or is null");
  475.                 } catch (ex) {
  476.                     LOG("modifyItem's onOperationComplete threw an"
  477.                           + " exception " + ex + "; ignoring");
  478.                 }
  479.             }
  480.  
  481.             return;
  482.         }
  483.  
  484.         if (aNewItem.parentItem != aNewItem) {
  485.             aNewItem.parentItem.recurrenceInfo.modifyException(aNewItem);
  486.             aNewItem = aNewItem.parentItem;
  487.         }
  488.  
  489.         var eventUri = this.mCalendarUri.clone();
  490.         eventUri.spec = this.makeUri(this.mItemInfoCache[aNewItem.id].locationPath);
  491.  
  492.         var modListener = {};
  493.         var thisCalendar = this;
  494.  
  495.         var modifiedItem = getIcsService().createIcalComponent("VCALENDAR");
  496.         calSetProdidVersion(modifiedItem);
  497.         modifiedItem.addSubcomponent(aNewItem.icalComponent);
  498.         if (aNewItem.recurrenceInfo) {
  499.             var exceptions = aNewItem.recurrenceInfo.getExceptionIds({});
  500.             for each (var exc in exceptions) {
  501.                 modifiedItem.addSubcomponent(aNewItem.recurrenceInfo.getExceptionFor(exc, true).icalComponent);
  502.             }
  503.         }
  504.         var modifiedItemICS = modifiedItem.serializeToICS();
  505.  
  506.         modListener.onStreamComplete = function(aLoader, aContext, aStatus,
  507.                                              aResultLength, aResult) {
  508.             // 201 = HTTP "Created"
  509.             // 204 = HTTP "No Content"
  510.             //
  511.             var status = aContext.responseStatus;
  512.             if (status == 204 || status == 201) {
  513.                 LOG("Item modified successfully.");
  514.                 var retVal = Components.results.NS_OK;
  515.                 // Some CalDAV servers will modify items on PUT (add X-props,
  516.                 // change location, etc) so we'd best re-fetch in order to know
  517.                 // the current state of the item
  518.                 // Observers will be notified in getUpdatedItem()
  519.                 thisCalendar.getUpdatedItem(aNewItem, aListener);
  520.             } else if (status == 412) {
  521.                 thisCalendar.promptOverwrite(CALDAV_MODIFY_ITEM, aNewItem,
  522.                                              aListener, aOldItem);
  523.             } else {
  524.                 if (status > 999) {
  525.                     status = "0x " + status.toString(16);
  526.                 }
  527.                 LOG("Error modifying item: " + status);
  528.  
  529.                 // XXX deal with non-existent item here, other
  530.                 // real error handling
  531.  
  532.                 // XXX aStatusCode will be 201 Created for a PUT on an item
  533.                 // that didn't exist before.
  534.  
  535.                 retVal = Components.results.NS_ERROR_FAILURE;
  536.             }
  537.             return;
  538.         }
  539.  
  540.         // XXX use etag as generation
  541.  
  542.         var httpchannel = this.prepChannel(eventUri, modifiedItemICS,
  543.                                            "text/calendar; charset=utf-8");
  544.  
  545.         if (!aIgnoreEtag) {
  546.             httpchannel.setRequestHeader("If-Match",
  547.                                          this.mItemInfoCache[aNewItem.id].etag,
  548.                                          false);
  549.         }
  550.  
  551.         LOG("modifyItem: PUTting = " + modifiedItemICS);
  552.         var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
  553.                              .createInstance(Components.interfaces
  554.                              .nsIStreamLoader);
  555.  
  556.         if (isOnBranch) {
  557.             streamLoader.init(httpchannel, modListener, httpchannel);
  558.         } else {
  559.             streamLoader.init(modListener);
  560.             httpchannel.asyncOpen(streamLoader, httpchannel);
  561.         }
  562.  
  563.         return;
  564.     },
  565.  
  566.     /**
  567.      * deleteItem(); required by calICalendar.idl
  568.      * the actual deletion is done in doDeleteItem()
  569.      *
  570.      * @param aItem       item to delete
  571.      * @param aListener   listener for method completion
  572.      */
  573.     deleteItem: function caldavDI(aItem, aListener) {
  574.         return this.doDeleteItem(aItem, aListener, false);
  575.     },
  576.  
  577.     /**
  578.      * Deletes item from CalDAV store.
  579.      *
  580.      * @param aItem       item to delete
  581.      * @param aListener   listener for method completion
  582.      * @param aIgnoreEtag ignore item etag
  583.      */
  584.     doDeleteItem: function caldavDDI(aItem, aListener, aIgnoreEtag) {
  585.  
  586.         if (aItem.id == null) {
  587.             if (aListener)
  588.                 aListener.onOperationComplete (this.superCalendar,
  589.                                                Components.results.NS_ERROR_FAILURE,
  590.                                                aListener.DELETE,
  591.                                                aItem.id,
  592.                                                "ID doesn't exist for deleteItem");
  593.             return;
  594.         }
  595.  
  596.         var eventUri = this.mCalendarUri.clone();
  597.         eventUri.spec = this.makeUri(this.mItemInfoCache[aItem.id].locationPath);
  598.  
  599.         var delListener = {};
  600.         var thisCalendar = this;
  601.         var realListener = aListener; // need to access from callback
  602.  
  603.         delListener.onStreamComplete =
  604.         function caldavDLoSC(aLoader, aContext, aStatus, aResultLength, aResult) {
  605.  
  606.             var status = aContext.responseStatus;
  607.             // 204 = HTTP "No content"
  608.             //
  609.             if (status == 204) {
  610.                 thisCalendar.mMemoryCalendar.deleteItem(aItem, aListener);
  611.                 delete thisCalendar.mHrefIndex[eventUri.path];
  612.                 delete thisCalendar.mItemInfoCache[aItem.id];
  613.                 LOG("Item deleted successfully.");
  614.                 var retVal = Components.results.NS_OK;
  615.             }
  616.             else if (status == 412) {
  617.                 // item has either been modified or deleted by someone else
  618.                 // check to see which
  619.  
  620.                 var httpchannel2 = thisCalendar.prepChannel(eventUri, null, null);
  621.                 httpchannel2.requestMethod = "HEAD";
  622.                 var streamLoader2 = Components.classes
  623.                                     ["@mozilla.org/network/stream-loader;1"]
  624.                                     .createInstance(Components.interfaces
  625.                                     .nsIStreamLoader);
  626.                 if (isOnBranch) {
  627.                     streamLoader2.init(httpchannel2, delListener2, httpchannel2);
  628.                 } else {
  629.                     streamLoader2.init(streamListener2);
  630.                     httpchannel2.asyncOpen(streamLoader2, httpchannel2);
  631.                 }
  632.  
  633.             } else {
  634.                 LOG("Error deleting item: " + status);
  635.                 // XXX real error handling here
  636.                 retVal = Components.results.NS_ERROR_FAILURE;
  637.             }
  638.         }
  639.         var delListener2 = {};
  640.         delListener2.onStreamComplete =
  641.         function caldavDL2oSC(aLoader, aContext, aStatus, aResultLength, aResult) {
  642.             var status2 = aContext.responseStatus;
  643.             if (status2 == 404) {
  644.                 // someone else already deleted it
  645.                 return;
  646.             } else {
  647.                 thisCalendar.promptOverwrite(CALDAV_DELETE_ITEM, aItem,
  648.                                              realListener, null);
  649.             }
  650.         }
  651.  
  652.         // XXX check generation
  653.         var httpchannel = this.prepChannel(eventUri, null, null);
  654.         if (!aIgnoreEtag) {
  655.             httpchannel.setRequestHeader("If-Match",
  656.                                          this.mItemInfoCache[aItem.id].etag,
  657.                                          false);
  658.         }
  659.         httpchannel.requestMethod = "DELETE";
  660.  
  661.         var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
  662.                              .createInstance(Components.interfaces
  663.                              .nsIStreamLoader);
  664.  
  665.         if (isOnBranch) {
  666.             streamLoader.init(httpchannel, delListener, httpchannel);
  667.         } else {
  668.             streamLoader.init(delListener);
  669.             httpchannel.asyncOpen(streamLoader, httpchannel);
  670.         }
  671.  
  672.         return;
  673.     },
  674.  
  675.     /**
  676.      * Retrieves a specific item from the CalDAV store.
  677.      * Use when an outdated copy of the item is in hand.
  678.      *
  679.      * @param aItem       item to fetch
  680.      * @param aListener   listener for method completion
  681.      */
  682.     getUpdatedItem: function caldavGUI(aItem, aListener) {
  683.  
  684.         if (aItem == null) {
  685.             if (aListener) {
  686.                 aListener.onOperationComplete(this.superCalendar,
  687.                                               Components.results.NS_ERROR_FAILURE,
  688.                                               aListener.GET,
  689.                                               null,
  690.                                               "passed in null item");
  691.             }
  692.             return;
  693.         }
  694.  
  695.         var itemType = "VEVENT";
  696.         if (aItem instanceof Components.interfaces.calITodo) {
  697.             itemType = "VTODO";
  698.         }
  699.  
  700.         var queryStatuses = new Array();
  701.  
  702.         var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
  703.         var D = new Namespace("D", "DAV:");
  704.         default xml namespace = C;
  705.  
  706.         queryXml =
  707.           <calendar-query xmlns:D="DAV:">
  708.             <D:prop>
  709.               <D:getetag/>
  710.               <calendar-data/>
  711.             </D:prop>
  712.             <filter>
  713.               <comp-filter name="VCALENDAR">
  714.                 <comp-filter name={itemType}>
  715.                   <prop-filter name="UID">
  716.                     <text-match collation="i;octet">
  717.                       {aItem.id}
  718.                     </text-match>
  719.                   </prop-filter>
  720.                 </comp-filter>
  721.               </comp-filter>
  722.             </filter>
  723.           </calendar-query>;
  724.  
  725.         this.reportInternal(xmlHeader + queryXml.toXMLString(), aItem, aListener);
  726.         return;
  727.  
  728.     },
  729.  
  730.     // void getItem( in string id, in calIOperationListener aListener );
  731.     getItem: function (aId, aListener) {
  732.         this.mMemoryCalendar.getItem(aId, aListener);
  733.         return;
  734.     },
  735.  
  736.     reportInternal: function caldavRI(aQuery, aItem, aListener)
  737.     {
  738.         var reportListener = new WebDavListener();
  739.         var thisCalendar = this; // need to access from inside the callback
  740.  
  741.         reportListener.onOperationDetail = function(aStatusCode, aResource,
  742.                                                     aOperation, aDetail,
  743.                                                     aClosure) {
  744.             var rv;
  745.             var errString;
  746.  
  747.             // is this detail the entire search result, rather than a single
  748.             // detail within a set?
  749.             //
  750.             if (aResource.path == calendarDirUri.path) {
  751.                 // XXX is this even valid?  what should we do here?
  752.                 // XXX if it's an error, it might be valid?
  753.                 LOG("XXX report result for calendar, not event\n");
  754.                 throw("XXX report result for calendar, not event\n");
  755.             }
  756.  
  757.             var items = null;
  758.  
  759.             // XXX need to do better than looking for just 200
  760.             if (aStatusCode == 200) {
  761.  
  762.                 // aDetail is the response element from the multi-status
  763.                 // XXX try-catch
  764.                 var xSerializer = Components.classes
  765.                     ['@mozilla.org/xmlextras/xmlserializer;1']
  766.                     .getService(Components.interfaces.nsIDOMSerializer);
  767.                 // libical needs to see \r\n instead on \n\n in the case of "folded" lines
  768.                 var response = xSerializer.serializeToString(aDetail).replace(/\n\n/g, "\r\n");
  769.                 var responseElement = new XML(response);
  770.  
  771.                 // create calIItemBase from e4x object
  772.                 // XXX error-check that we only have one result, etc
  773.                 var C = new Namespace("urn:ietf:params:xml:ns:caldav");
  774.                 var D = new Namespace("DAV:");
  775.  
  776.                 var etag = responseElement..D::["getetag"];
  777.  
  778.                 // cause returned data to be parsed into the item
  779.                 var calData = responseElement..C::["calendar-data"];
  780.                 if (!calData.toString().length) {
  781.                   Components.utils.reportError(
  782.                     "Empty or non-existent <calendar-data> element returned" +
  783.                     " by CalDAV server for URI <" + aResource.spec +
  784.                     ">; ignoring");
  785.                   return;
  786.                 }
  787.                 // LOG("item result = \n" + calData);
  788.                 var rootComp = getIcsService().parseICS(calData, null);
  789.  
  790.                 var calComp;
  791.                 if (rootComp.componentType == 'VCALENDAR') {
  792.                     calComp = rootComp;
  793.                 } else {
  794.                     calComp = rootComp.getFirstSubcomponent('VCALENDAR');
  795.                 }
  796.  
  797.                 var unexpandedItems = [];
  798.                 var uid2parent = {};
  799.                 var excItems = [];
  800.  
  801.                 while (calComp) {
  802.                     // Get unknown properties
  803.                     var prop = calComp.getFirstProperty("ANY");
  804.                     while (prop) {
  805.                         thisCalendar.unmappedProperties.push(prop);
  806.                         prop = calComp.getNextProperty("ANY");
  807.                     }
  808.  
  809.                     var subComp = calComp.getFirstSubcomponent("ANY");
  810.                     while (subComp) {
  811.                         // Place each subcomp in a try block, to hopefully get as
  812.                         // much of a bad calendar as possible
  813.                         try {
  814.                             var item = null;
  815.                             switch (subComp.componentType) {
  816.                             case "VEVENT":
  817.                                 item = Components.classes["@mozilla.org/calendar/event;1"].
  818.                                        createInstance(Components.interfaces.calIEvent);
  819.                                 break;
  820.                             case "VTODO":
  821.                                 item = Components.classes["@mozilla.org/calendar/todo;1"].
  822.                                        createInstance(Components.interfaces.calITodo);
  823.                                 break;
  824.                             case "VTIMEZONE":
  825.                                 // we should already have this, so there's no need to
  826.                                 // do anything with it here.
  827.                                 break;
  828.                             default:
  829.                                 thisCalendar.unmappedComponents.push(subComp);
  830.                                 break;
  831.                             }
  832.                             if (item != null) {
  833.  
  834.                                 item.icalComponent = subComp;
  835.                                 // save the location name in case we need to modify
  836.                                 // need to build using thisCalendar since aResource.spec
  837.                                 // won't contain any auth info embedded in the URI
  838.                                 var locationPath = decodeURIComponent(aResource.path)
  839.                                                    .substr(thisCalendar.mLocationPath.length);
  840.                                 if (!thisCalendar.mItemInfoCache[item.id]) {
  841.                                     thisCalendar.mItemInfoCache[item.id] = {};
  842.                                 }
  843.                                 thisCalendar.mItemInfoCache[item.id].locationPath =
  844.                                     locationPath;
  845.                                 thisCalendar.mHrefIndex[aResource.path] = item.id;
  846.                                 var rid = item.recurrenceId;
  847.                                 if (rid == null) {
  848.                                     unexpandedItems.push( item );
  849.                                     if (item.recurrenceInfo != null) {
  850.                                         uid2parent[item.id] = item;
  851.                                     }
  852.                                 } else {
  853.                                     item.calendar = thisCalendar.superCalendar;
  854.                                     // force no recurrence info so we can
  855.                                     // rebuild it cleanly below
  856.                                     item.recurrenceInfo = null;
  857.                                     excItems.push(item);
  858.                                 }
  859.                             }
  860.                         } catch (ex) {
  861.                             thisCalendar.mObservers.notify("onError", [ex.result, ex.toString()]);
  862.                         }
  863.                         subComp = calComp.getNextSubcomponent("ANY");
  864.                     }
  865.                     calComp = rootComp.getNextSubcomponent('VCALENDAR');
  866.                 }
  867.  
  868.                 // tag "exceptions", i.e. items with rid:
  869.                 for each (var item in excItems) {
  870.                     var parent = uid2parent[item.id];
  871.                     if (parent == null) {
  872.                         LOG( "no parent item for rid=" + item.recurrenceId );
  873.                     } else {
  874.                         item.parentItem = parent;
  875.                         item.parentItem.recurrenceInfo.modifyException(item);
  876.                     }
  877.                 }
  878.                 // if we loop over both excItems and unexpandedItems using 'item'
  879.                 // we can be confident that 'item' means something below
  880.                 for each (var item in unexpandedItems) {
  881.                     item.calendar = thisCalendar.superCalendar;
  882.                 }
  883.  
  884.                 thisCalendar.mItemInfoCache[item.id].etag = etag;
  885.  
  886.                 // figure out what type of item to return
  887.                 var iid;
  888.                 if (item instanceof Components.interfaces.calIEvent) {
  889.                     iid = Components.interfaces.calIEvent;
  890.                     rv = Components.results.NS_OK;
  891.                     items = [ item ];
  892.                 } else if (item instanceof Components.interfaces.calITodo) {
  893.                     iid = Components.interfaces.calITodo;
  894.                     rv = Components.results.NS_OK;
  895.                     items = [ item ];
  896.                 } else {
  897.                     errString = "Can't deduce item type based on query";
  898.                     rv = Components.results.NS_ERROR_FAILURE;
  899.                 }
  900.  
  901.             } else {
  902.                 // XXX
  903.                 LOG("aStatusCode = " + aStatusCode);
  904.                 errString = "XXX";
  905.                 rv = Components.results.NS_ERROR_FAILURE;
  906.             }
  907.  
  908.             if (errString) {
  909.                 LOG("errString = " + errString);
  910.             }
  911.  
  912.             for (var i = 0; i < items.length; i++) {
  913.                 if (thisCalendar.mItemInfoCache[items[i].id]) {
  914.                     thisCalendar.mMemoryCalendar.modifyItem(items[i], null,
  915.                                                             aListener);
  916.                 } else {
  917.                     thisCalendar.mMemoryCalendar.adoptItem(items[i], aListener);
  918.                 }
  919.             }
  920.             return;
  921.         };
  922.  
  923.         reportListener.onOperationComplete = function(aStatusCode, aResource,
  924.                                                       aOperation, aClosure) {
  925.             LOG("refresh completed with status " + aStatusCode);
  926.             thisCalendar.mObservers.notify("onLoad", [thisCalendar]);
  927.         };
  928.  
  929.         // convert this into a form the WebDAV service can use
  930.         var xParser = Components.classes['@mozilla.org/xmlextras/domparser;1']
  931.                       .getService(Components.interfaces.nsIDOMParser);
  932.         queryDoc = xParser.parseFromString(aQuery, "application/xml");
  933.  
  934.         // construct the resource we want to search against
  935.         var calendarDirUri = this.mCalendarUri.clone();
  936.         calendarDirUri.spec = this.makeUri('');
  937.         // LOG("report uri = " + calendarDirUri.spec);
  938.         var calendarDirResource = new WebDavResource(calendarDirUri);
  939.  
  940.         var webSvc = Components.classes['@mozilla.org/webdav/service;1']
  941.             .getService(Components.interfaces.nsIWebDAVService);
  942.         webSvc.report(calendarDirResource, queryDoc, true, reportListener,
  943.                       this, null);
  944.         return;
  945.  
  946.     },
  947.  
  948.     // void getItems( in unsigned long aItemFilter, in unsigned long aCount,
  949.     //                in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
  950.     //                in calIOperationListener aListener );
  951.     getItems: function caldav_getItems(aItemFilter, aCount, aRangeStart,
  952.                                        aRangeEnd, aListener) {
  953.  
  954.  
  955.         this.mMemoryCalendar.getItems(aItemFilter, aCount, aRangeStart,
  956.                                       aRangeEnd, aListener);
  957.     },
  958.  
  959.     refresh: function caldav_refresh() {
  960.         if (this.mAuthScheme != "Digest") {
  961.             // Basic HTTP Auth will not have timed out, we can just refresh
  962.             // Same for Cosmo ticket-based authentication
  963.             this.safeRefresh();
  964.         } else {
  965.             // Digest auth may have timed out, and we need to make sure that
  966.             // several calendars in this realm do not attempt re-auth simultaneously
  967.             if (this.firstInRealm()) {
  968.                 this.safeRefresh();
  969.             }
  970.         }
  971.     },
  972.  
  973.     firstInRealm: function caldav_firstInRealm() {
  974.         var calendars = getCalendarManager().getCalendars({});
  975.         for (var i = 0; i < calendars.length ; i++) {
  976.             if (calendars[i].type != "caldav") {
  977.                 continue;
  978.             }
  979.             if (calendars[i].uri.prePath == this.uri.prePath &&
  980.             calendars[i].QueryInterface(calICalDavCalendar)
  981.                         .authRealm == this.mAuthRealm) {
  982.                 if (calendars[i].id == this.id) {
  983.                     return true;
  984.                 }
  985.                 break;
  986.             }
  987.         }
  988.         return false;
  989.     },
  990.  
  991.     refreshOtherCals: function caldav_refreshOtherCals() {
  992.         var calendars = getCalendarManager().getCalendars({});
  993.         for (var i = 0; i < calendars.length ; i++) {
  994.             if (calendars[i].type == "caldav" &&
  995.                 calendars[i].uri.prePath == this.uri.prePath &&
  996.                 calendars[i].QueryInterface(calICalDavCalendar)
  997.                             .authRealm == this.mAuthRealm &&
  998.                 calendars[i].id != this.id) {
  999.                 calendars[i].safeRefresh();
  1000.             }
  1001.         }
  1002.     },
  1003.  
  1004.     safeRefresh: function caldav_safeRefresh() {
  1005.  
  1006.         var itemTypes = new Array("VEVENT", "VTODO");
  1007.         var typesCount = itemTypes.length;
  1008.         var refreshEvent = {};
  1009.         refreshEvent.itemTypes = itemTypes;
  1010.         refreshEvent.typesCount = typesCount;
  1011.         refreshEvent.queryStatuses = [];
  1012.         refreshEvent.itemsNeedFetching = [];
  1013.         refreshEvent.itemsReported = [];
  1014.  
  1015.         this.getUpdatedItems(refreshEvent);
  1016.  
  1017.     },
  1018.  
  1019.  
  1020.     getUpdatedItems: function caldav_GUIs(aRefreshEvent) {
  1021.  
  1022.         if (this.mDisabled) {
  1023.             // check if maybe our calendar has become available
  1024.             this.checkDavResourceType();
  1025.             return;
  1026.         }
  1027.  
  1028.         if (aRefreshEvent.itemTypes.length) {
  1029.             var itemType = aRefreshEvent.itemTypes.pop();
  1030.         } else {
  1031.             return;
  1032.         }
  1033.  
  1034.         var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
  1035.         var D = new Namespace("D", "DAV:");
  1036.         default xml namespace = C;
  1037.  
  1038.         var queryXml =
  1039.           <calendar-query xmlns:D={D}>
  1040.             <D:prop>
  1041.               <D:getetag/>
  1042.             </D:prop>
  1043.             <filter>
  1044.               <comp-filter name="VCALENDAR">
  1045.                 <comp-filter/>
  1046.               </comp-filter>
  1047.             </filter>
  1048.           </calendar-query>;
  1049.  
  1050.         queryXml[0].C::filter.C::["comp-filter"]
  1051.                         .C::["comp-filter"] =
  1052.                         <comp-filter name={itemType}/>;
  1053.  
  1054.  
  1055.         var queryString = xmlHeader + queryXml.toXMLString();
  1056.  
  1057.         var multigetQueryXml =
  1058.           <calendar-multiget xmlns:D={D}>
  1059.             <D:prop>
  1060.               <D:getetag/>
  1061.               <calendar-data/>
  1062.             </D:prop>
  1063.           </calendar-multiget>;
  1064.  
  1065.         var etagListener = new WebDavListener();
  1066.         var thisCalendar = this;
  1067.  
  1068.         etagListener.onOperationDetail = function(aStatusCode, aResource,
  1069.                                                     aOperation, aDetail,
  1070.                                                     aClosure) {
  1071.             var xSerializer = Components.classes
  1072.                 ['@mozilla.org/xmlextras/xmlserializer;1']
  1073.                 .getService(Components.interfaces.nsIDOMSerializer);
  1074.             // libical needs to see \r\n instead on \n\n in the case of
  1075.             // "folded" lines
  1076.             var response = xSerializer.serializeToString(aDetail).
  1077.                                        replace(/\n\n/g, "\r\n");
  1078.             var responseElement = new XML(response);
  1079.  
  1080.             var etag = responseElement..D::["getetag"];
  1081.  
  1082.             aRefreshEvent.itemsReported.push(aResource.path);
  1083.  
  1084.             if (thisCalendar.mHrefIndex[aResource.path]) {
  1085.                 var itemuid = thisCalendar.mHrefIndex[aResource.path];
  1086.                 if (etag != thisCalendar.mItemInfoCache[itemuid].etag) {
  1087.                     // we don't have a current copy in cache; fetch the item
  1088.                     aRefreshEvent.itemsNeedFetching.push(aResource.path);
  1089.                 }
  1090.             } else {
  1091.                 aRefreshEvent.itemsNeedFetching.push(aResource.path);
  1092.             }
  1093.         }
  1094.  
  1095.         etagListener.onOperationComplete = function(aStatusCode, aResource,
  1096.                                                       aOperation, aClosure) {
  1097.             aRefreshEvent.queryStatuses.push(aStatusCode);
  1098.             var needsRefresh = false;
  1099.             if (aRefreshEvent.queryStatuses.length == aRefreshEvent.typesCount) {
  1100.  
  1101.                for each (var statusCode in aRefreshEvent.queryStatuses) {
  1102.                     if (statusCode != 207) { // XXX better error checking
  1103.                         LOG("error fetching item etags: " + statusCode);
  1104.                     }
  1105.                 }
  1106.  
  1107.                 // if an item has been deleted from the server, delete it here too
  1108.                 for (var path in  thisCalendar.mHrefIndex) {
  1109.                     if (aRefreshEvent.itemsReported.indexOf(path) < 0) {
  1110.  
  1111.                         var getItemListener = {};
  1112.                         getItemListener.onGetResult = function caldav_gUIs_oGR(aCalendar,
  1113.                             aStatus, aItemType, aDetail, aCount, aItems) {
  1114.                             var itemToDelete = aItems[0];
  1115.                             delete thisCalendar.mItemInfoCache[itemToDelete.id];
  1116.                             thisCalendar.mMemoryCalendar.deleteItem(itemToDelete,
  1117.                                                                      getItemListener);
  1118.                             delete thisCalendar.mHrefIndex[path];
  1119.                             needsRefresh = true;
  1120.                         }
  1121.                         getItemListener.onOperationComplete = function
  1122.                             caldav_gUIs_oOC(aCalendar, aStatus, aOperationType,
  1123.                                             aId, aDetail) {}
  1124.                         thisCalendar.mMemoryCalendar.getItem(thisCalendar.mHrefIndex[path],
  1125.                                                              getItemListener);
  1126.                     }
  1127.                 }
  1128.  
  1129.                 // avoid sending empty multiget requests
  1130.                 // update views if something has been deleted server-side
  1131.                 if (!aRefreshEvent.itemsNeedFetching.length) {
  1132.                     if (needsRefresh) {
  1133.                         thisCalendar.mObservers.notify("onLoad", [thisCalendar]);
  1134.                     }
  1135.                     return;
  1136.                 }
  1137.  
  1138.                 while (aRefreshEvent.itemsNeedFetching.length > 0) {
  1139.                     var locpath = aRefreshEvent.itemsNeedFetching.pop();
  1140.                     var hrefXml = new XML();
  1141.                     hrefXml = <hr xmlns:D={D}/>
  1142.                     hrefXml.D::href = locpath;
  1143.                     multigetQueryXml[0].appendChild(hrefXml.D::href);
  1144.                 }
  1145.  
  1146.                 var multigetQueryString = xmlHeader +
  1147.                                           multigetQueryXml.toXMLString();
  1148.                 thisCalendar.reportInternal(multigetQueryString, null, null);
  1149.  
  1150.                 if (thisCalendar.mAuthScheme == "Digest" &&
  1151.                     thisCalendar.firstInRealm()) {
  1152.                     thisCalendar.refreshOtherCals();
  1153.                 }
  1154.  
  1155.             } else {
  1156.                 thisCalendar.getUpdatedItems(aRefreshEvent);
  1157.             }
  1158.         }
  1159.         var xParser = Components.classes['@mozilla.org/xmlextras/domparser;1']
  1160.                       .getService(Components.interfaces.nsIDOMParser);
  1161.         queryDoc = xParser.parseFromString(queryString, "application/xml");
  1162.  
  1163.         // construct the resource we want to search against
  1164.         var calendarDirUri = this.mCalendarUri.clone();
  1165.         calendarDirUri.spec = this.makeUri('');
  1166.         var calendarDirResource = new WebDavResource(calendarDirUri);
  1167.  
  1168.         var webSvc = Components.classes['@mozilla.org/webdav/service;1']
  1169.             .getService(Components.interfaces.nsIWebDAVService);
  1170.         webSvc.report(calendarDirResource, queryDoc, true, etagListener,
  1171.                       this, null);
  1172.         return;
  1173.     },
  1174.  
  1175.     // nsIInterfaceRequestor impl
  1176.     getInterface: function(iid) {
  1177.         if (iid.equals(Components.interfaces.nsIAuthPrompt)) {
  1178.             return new calAuthPrompt();
  1179.         }
  1180.         else if (iid.equals(Components.interfaces.nsIPrompt)) {
  1181.             // use the window watcher service to get a nsIPrompt impl
  1182.             return Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  1183.                              .getService(Components.interfaces.nsIWindowWatcher)
  1184.                              .getNewPrompter(null);
  1185.         } else if (iid.equals(Components.interfaces.nsIProgressEventSink)) {
  1186.             return this;
  1187.         // Needed for Lightning on branch vvv
  1188.         } else if (iid.equals(Components.interfaces.nsIDocShellTreeItem)) {
  1189.             return this;
  1190.         } else if (iid.equals(Components.interfaces.nsIAuthPromptProvider)) {
  1191.             return Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  1192.                              .getService(Components.interfaces.nsIWindowWatcher)
  1193.                              .getNewPrompter(null);
  1194.         } else if (!isOnBranch && iid.equals(Components.interfaces.nsIAuthPrompt2)) {
  1195.             return new calAuthPrompt();
  1196.         }
  1197.         throw Components.results.NS_ERROR_NO_INTERFACE;
  1198.     },
  1199.  
  1200.     //
  1201.     // Helper functions
  1202.     //
  1203.  
  1204.     // Unless an error number is in this array, we consider it very bad, set
  1205.     // the calendar to readOnly, and give up.
  1206.     acceptableErrorNums: [],
  1207.  
  1208.     onError: function caldav_onError(aErrNo, aMessage) {
  1209.         var errorIsOk = false;
  1210.         for each (num in this.acceptableErrorNums) {
  1211.             if (num == aErrNo) {
  1212.                 errorIsOk = true;
  1213.                 break;
  1214.             }
  1215.         }
  1216.         if (!errorIsOk) {
  1217.             this.mReadOnly = true;
  1218.             this.mDisabled = true;
  1219.         }
  1220.  
  1221.         var paramBlock = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  1222.                                    .createInstance(Components.interfaces
  1223.                                    .nsIDialogParamBlock);
  1224.         paramBlock.SetNumberStrings(3);
  1225.  
  1226.         var promptMessage = calGetString("calendar", "disabledMode", [this.name]);
  1227.         paramBlock.SetString(0, promptMessage);
  1228.         var errCode = "0x"+aErrNo.toString(16);
  1229.         paramBlock.SetString(1, errCode);
  1230.         paramBlock.SetString(2, aMessage);
  1231.         var wWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  1232.                                  .getService(Components.interfaces.nsIWindowWatcher);
  1233.         wWatcher.openWindow(null,
  1234.                             "chrome://calendar/content/calErrorPrompt.xul",
  1235.                             "_blank",
  1236.                             "chrome,dialog=yes",
  1237.                             paramBlock);
  1238.  
  1239.         if (this.mDisabled) {
  1240.             this.refresh();
  1241.         }
  1242.     },
  1243.  
  1244.     /**
  1245.      * Checks that the calendar URI exists and is a CalDAV calendar
  1246.      *
  1247.      */
  1248.     checkDavResourceType: function checkDavResourceType() {
  1249.         var resourceTypeXml = null;
  1250.         var resourceType = kDavResourceTypeNone;
  1251.         var thisCalendar = this;
  1252.  
  1253.         var D = new Namespace("D", "DAV:");
  1254.         var queryXml = <D:propfind xmlns:D="DAV:">
  1255.                         <D:prop>
  1256.                             <D:resourcetype/>
  1257.                         </D:prop>
  1258.                         </D:propfind>;
  1259.  
  1260.         var httpchannel = this.prepChannel(this.mUri,queryXml,
  1261.                                            "text/xml; charset=utf-8");
  1262.         httpchannel.setRequestHeader("Depth", "0", false);
  1263.         httpchannel.requestMethod = "PROPFIND";
  1264.  
  1265.         var streamListener = {};
  1266.  
  1267.         streamListener.onStreamComplete =
  1268.             function checkDavResourceType_oSC(aLoader, aContext, aStatus,
  1269.                                          aResultLength, aResult) {
  1270.             var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
  1271.                                              .createInstance(Components
  1272.                                              .interfaces.nsIScriptableUnicodeConverter);
  1273.  
  1274.             var wwwauth = aContext.getRequestHeader("Authorization");
  1275.  
  1276.             if (this.mUriParams) {
  1277.                 thisCalendar.mAuthScheme = "Ticket";
  1278.             } else {
  1279.                 thisCalendar.mAuthScheme = wwwauth.split(" ")[0];
  1280.             }
  1281.  
  1282.             // we only really need the authrealm for Digest auth
  1283.             // since only Digest is going to time out on us
  1284.             if (thisCalendar.mAuthScheme == "Digest") {
  1285.                 var realmChop = wwwauth.split("realm=\"")[1];
  1286.                 thisCalendar.mAuthRealm = realmChop.split("\", ")[0];
  1287.             }
  1288.  
  1289.             resultConverter.charset = "UTF-8";
  1290.             var str;
  1291.             try {
  1292.                 str = resultConverter.convertFromByteArray(aResult, aResultLength);
  1293.             } catch(e) {
  1294.                 LOG("Failed to determine resource type");
  1295.             }
  1296.             str = str.substring(str.indexOf('\n'));
  1297.             var multistatus = new XML(str);
  1298.  
  1299.             var resourceTypeXml = multistatus..D::["resourcetype"];
  1300.             if (resourceTypeXml.length == 0) {
  1301.                 resourceType = kDavResourceTypeNone;
  1302.             } else if (resourceTypeXml.toString().indexOf("calendar") != -1) {
  1303.                 resourceType = kDavResourceTypeCalendar;
  1304.             } else if (resourceTypeXml.toString().indexOf("collection") != -1) {
  1305.                 resourceType = kDavResourceTypeCollection;
  1306.             }
  1307.  
  1308.             if ((resourceType == null || resourceType == kDavResourceTypeNone) &&
  1309.                 !thisCalendar.mDisabled) {
  1310.                 thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_NOT_DAV,
  1311.                                             "dav_notDav");
  1312.             }
  1313.  
  1314.             if ((resourceType == kDavResourceTypeCollection) &&
  1315.                 !thisCalendar.mDisabled) {
  1316.                 thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_DAV_NOT_CALDAV,
  1317.                                             "dav_davNotCaldav");
  1318.             }
  1319.  
  1320.             // if this calendar was previously offline we want to recover
  1321.             if ((resourceType == kDavResourceTypeCalendar) &&
  1322.                 thisCalendar.mDisabled) {
  1323.                 thisCalendar.mDisabled = false;
  1324.                 thisCalendar.mReadOnly = false;
  1325.             }
  1326.  
  1327.             // we've authenticated in the process of PROPFINDing and can flush
  1328.             // the getItems request queue
  1329.             thisCalendar.setCalHomeSet();
  1330.             thisCalendar.checkServerCaps();
  1331.         }
  1332.         var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
  1333.                              .createInstance(Components.interfaces
  1334.                              .nsIStreamLoader);
  1335.  
  1336.         if (isOnBranch) {
  1337.             streamLoader.init(httpchannel, streamListener, httpchannel);
  1338.         } else {
  1339.             streamLoader.init(streamListener);
  1340.             httpchannel.asyncOpen(streamLoader, httpchannel);
  1341.         }
  1342.     },
  1343.  
  1344.     reportDavError: function caldav_rDE(aErrNo, aMessage) {
  1345.         this.onError(aErrNo, calGetString("calendar", aMessage, [this.mUri.spec]));
  1346.     },
  1347.  
  1348.     /**
  1349.      * Checks server capabilities
  1350.      * currently just calendar-schedule
  1351.      *
  1352.      */
  1353.     checkServerCaps: function caldav_checkServerCaps() {
  1354.  
  1355.         var homeSet = this.mCalHomeSet.clone();
  1356.         var thisCalendar = this;
  1357.  
  1358.         var httpchannel = this.prepChannel(homeSet, null, null);
  1359.  
  1360.         httpchannel.requestMethod = "OPTIONS";
  1361.  
  1362.         var streamListener = {};
  1363.  
  1364.         streamListener.onStreamComplete =
  1365.             function checkServerCaps_oSC(aLoader, aContext, aStatus,
  1366.                                          aResultLength, aResult) {
  1367.             var dav = aContext.getResponseHeader("DAV");
  1368.  
  1369.             if (dav.indexOf("calendar-schedule") != -1) {
  1370.                 thisCalendar.mHaveScheduling = true;
  1371.                 // XXX - we really shouldn't register with the fb service
  1372.                 // if another calendar with the same principal-URL has already
  1373.                 // done so
  1374.                 getFreeBusyService().addProvider(thisCalendar);
  1375.                 thisCalendar.findPrincipalNS();
  1376.             } else {
  1377.                 LOG("Server does not support CalDAV scheduling.");
  1378.                 thisCalendar.refresh();
  1379.             }
  1380.         }
  1381.  
  1382.         var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
  1383.                              .createInstance(Components.interfaces
  1384.                              .nsIStreamLoader);
  1385.  
  1386.         if (isOnBranch) {
  1387.             streamLoader.init(httpchannel, streamListener, httpchannel);
  1388.         } else {
  1389.             streamLoader.init(streamListener);
  1390.             httpchannel.asyncOpen(streamLoader, httpchannel);
  1391.         }
  1392.  
  1393.     },
  1394.  
  1395.     /**
  1396.      * Locates the principal namespace
  1397.      */
  1398.     findPrincipalNS: function caldav_findPrincipalNS() {
  1399.  
  1400.         var homeSet = this.mCalHomeSet.clone();
  1401.         var thisCalendar = this;
  1402.  
  1403.         var D = new Namespace("D", "DAV:");
  1404.         var queryXml = <D:propfind xmlns:D="DAV:">
  1405.                     <D:prop>
  1406.                       <D:principal-collection-set/>
  1407.                     </D:prop>
  1408.                   </D:propfind>
  1409.  
  1410.         var httpchannel = this.prepChannel(homeSet, queryXml,
  1411.                                            "text/xml; charset=utf-8");
  1412.  
  1413.         httpchannel.setRequestHeader("Depth", "0", false);
  1414.         httpchannel.requestMethod = "PROPFIND";
  1415.  
  1416.         var streamListener = {};
  1417.  
  1418.         streamListener.onStreamComplete =
  1419.             function findInOutBoxes_oSC(aLoader, aContext, aStatus,
  1420.                                          aResultLength, aResult) {
  1421.             var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
  1422.                                              .createInstance(Components
  1423.                                              .interfaces.nsIScriptableUnicodeConverter);
  1424.  
  1425.             resultConverter.charset = "UTF-8";
  1426.             var str;
  1427.             try {
  1428.                 str = resultConverter.convertFromByteArray(aResult, aResultLength);
  1429.             } catch(e) {
  1430.                 LOG("Failed to propstat principal namespace");
  1431.             }
  1432.             str = str.substring(str.indexOf('\n'));
  1433.             var multistatus = new XML(str);
  1434.             var pnsUri = thisCalendar.mUri.clone();
  1435.             var pcs = multistatus..D::["principal-collection-set"]..D::href;
  1436.             if (pcs.charAt(pcs.length-1) != '/') {
  1437.                 pcs += "/";
  1438.             }
  1439.  
  1440.             pnsUri.path = thisCalendar.ensurePath(pcs);
  1441.             thisCalendar.mPrincipalsNS = pnsUri;
  1442.             thisCalendar.checkPrincipalsNameSpace();
  1443.         }
  1444.  
  1445.         var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
  1446.                              .createInstance(Components.interfaces
  1447.                              .nsIStreamLoader);
  1448.  
  1449.         if (isOnBranch) {
  1450.             streamLoader.init(httpchannel, streamListener, httpchannel);
  1451.         } else {
  1452.             streamLoader.init(streamListener);
  1453.             httpchannel.asyncOpen(streamLoader, httpchannel);
  1454.         }
  1455.     },
  1456.  
  1457.     /**
  1458.      * Checks the principals namespace for scheduling info
  1459.      */
  1460.     checkPrincipalsNameSpace: function caldav_cPNS() {
  1461.  
  1462.         var pns = this.mPrincipalsNS.clone();
  1463.         var thisCalendar = this;
  1464.  
  1465.         var homePath = this.mCalHomeSet.path;
  1466.         if (homePath.charAt(homePath.length-1) == '/') {
  1467.             homePath = homePath.substr(0, homePath.length-1);
  1468.         }
  1469.  
  1470.         var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
  1471.         var D = new Namespace("D", "DAV:");
  1472.         default xml namespace = C;
  1473.  
  1474.         var queryXml = <D:principal-property-search xmlns:D="DAV:"
  1475.                 xmlns:C="urn:ietf:params:xml:ns:caldav">
  1476.             <D:property-search>
  1477.                 <D:prop>
  1478.                     <C:calendar-home-set/>
  1479.                 </D:prop>
  1480.                 <D:match>{homePath}</D:match>
  1481.             </D:property-search>
  1482.                 <D:prop>
  1483.                     <C:calendar-home-set/>
  1484.                     <C:calendar-user-address-set/>
  1485.                     <C:schedule-inbox-URL/>
  1486.                     <C:schedule-outbox-URL/>
  1487.                 </D:prop>
  1488.             </D:principal-property-search>;
  1489.  
  1490.         var httpchannel = this.prepChannel(pns, queryXml,
  1491.                                            "text/xml; charset=utf-8");
  1492.  
  1493.         httpchannel.requestMethod = "REPORT";
  1494.  
  1495.         var streamListener = {};
  1496.  
  1497.         streamListener.onStreamComplete =
  1498.             function caldav_cPNS_oSC(aLoader, aContext, aStatus,
  1499.                                          aResultLength, aResult) {
  1500.             if (aContext.responseStatus != 207) {
  1501.                 thisCalendar.mHaveScheduling = false;
  1502.                 thisCalendar.mInBoxUrl = null;
  1503.                 thisCalendar.mOutBoxUrl = null;
  1504.                 return;
  1505.             }
  1506.             var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
  1507.                                              .createInstance(Components
  1508.                                              .interfaces.nsIScriptableUnicodeConverter);
  1509.  
  1510.             resultConverter.charset = "UTF-8";
  1511.             var str;
  1512.             try {
  1513.                 str = resultConverter.convertFromByteArray(aResult, aResultLength);
  1514.             } catch(e) {
  1515.                 LOG("Failed to report principals namespace");
  1516.             }
  1517.             thisCalendar.mMailToUrl = thisCalendar.mCalendarUri.spec;
  1518.  
  1519.             if (str.substr(0,6) == "<?xml ") {
  1520.                 str = str.substring(str.indexOf('\n'));
  1521.             }
  1522.             var multistatus = new XML(str);
  1523.  
  1524.             for (var i = 0; i < multistatus.*.length(); i++) {
  1525.                 var response = new XML(multistatus.*[i]);
  1526.  
  1527.                 var responseCHS = response..C::["calendar-home-set"]..D::href[0];
  1528.                 if (!responseCHS) {
  1529.                     responseCHS = response..D::["calendar-home-set"]..D::href[0];
  1530.                 }
  1531.  
  1532.                 if (responseCHS.charAt(responseCHS.toString().length -1) != "/") {
  1533.                     responseCHS += "/";
  1534.                 }
  1535.  
  1536.                 if (responseCHS  != thisCalendar.mCalHomeSet.path &&
  1537.                     responseCHS != thisCalendar.mCalHomeSet.spec) {
  1538.                     continue;
  1539.                 }
  1540.                 var addrHrefs =
  1541.                     response..C::["calendar-user-address-set"]..D::href;
  1542.                 if (!addrHrefs.toString().length) {
  1543.                     var addrHrefs =
  1544.                         response..D::propstat..D::["calendar-user-address-set"]..D::href;
  1545.                 }
  1546.                 for (var j = 0; j < addrHrefs.*.length(); j++) {
  1547.                     if (addrHrefs[j].substr(0,7).toLowerCase() == "mailto:") {
  1548.                         thisCalendar.mMailToUrl = addrHrefs[j];
  1549.                     }
  1550.                 }
  1551.                 var ibUrl = thisCalendar.mUri.clone();
  1552.                 var ibPath =
  1553.                     response..C::["schedule-inbox-URL"]..D::href[0];
  1554.                 if (!ibPath) {
  1555.                     var ibPath = response..D::["schedule-inbox-URL"]..D::href[0];
  1556.                 }
  1557.                 ibUrl.path = thisCalendar.ensurePath(ibPath);
  1558.                 thisCalendar.mInBoxUrl = ibUrl;
  1559.                 var obUrl = thisCalendar.mUri.clone();
  1560.                 var obPath =
  1561.                     response..C::["schedule-outbox-URL"]..D::href[0];
  1562.                 if (!obPath) {
  1563.                     var obPath = response..D::["schedule-outbox-URL"]..D::href[0];
  1564.                 }
  1565.                 obUrl.path = thisCalendar.ensurePath(obPath);
  1566.                 thisCalendar.mOutBoxUrl = obUrl;
  1567.             }
  1568.             thisCalendar.refresh();
  1569.         }
  1570.  
  1571.         var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
  1572.                              .createInstance(Components.interfaces
  1573.                              .nsIStreamLoader);
  1574.  
  1575.         if (isOnBranch) {
  1576.             streamLoader.init(httpchannel, streamListener, httpchannel);
  1577.         } else {
  1578.             streamLoader.init(streamListener);
  1579.             httpchannel.asyncOpen(streamLoader, httpchannel);
  1580.         }
  1581.         return;
  1582.     },
  1583.  
  1584.     //
  1585.     // calIFreeBusyProvider interface
  1586.     //
  1587.  
  1588.     getFreeBusyIntervals: function caldav_getFreeBusyIntervals(
  1589.         aCalId, aRangeStart, aRangeEnd, aBusyTypes, aListener) {
  1590.  
  1591.         if (!this.mHaveScheduling || !this.mOutBoxUrl || !this.mMailToUrl) {
  1592.             LOG("Server does not support scheduling; freebusy query not possible");
  1593.             return;
  1594.         }
  1595.  
  1596.         // the caller prepends MAILTO: to calid strings containing @
  1597.         // but apple needs that to be mailto:
  1598.         var aCalIdParts = aCalId.split(":");
  1599.         aCalIdParts[0] = aCalIdParts[0].toLowerCase();
  1600.  
  1601.         if (aCalIdParts[0] != "mailto"
  1602.             && aCalIdParts[0] != "http"
  1603.             && aCalIdParts[0] != "https" ) {
  1604.             return;
  1605.         }
  1606.         mailto_aCalId = aCalIdParts.join(":");
  1607.  
  1608.         var outBoxUri = this.mOutBoxUrl.clone();
  1609.         var thisCalendar = this;
  1610.  
  1611.         var organizer = this.mMailToUrl;
  1612.  
  1613.         var dtstamp = now().getInTimezone(UTC()).icalString;
  1614.         var dtstart = aRangeStart.getInTimezone(UTC()).icalString;
  1615.         var dtend = aRangeEnd.getInTimezone(UTC()).icalString;
  1616.         var uuid = getUUID();
  1617.  
  1618.         var fbQuery = "BEGIN:VCALENDAR\n";
  1619.         fbQuery += "VERSION:" + calGetProductVersion() + "\n";
  1620.         fbQuery += "PRODID:-" + calGetProductId() + "\n";
  1621.         fbQuery += "METHOD:REQUEST\n";
  1622.         fbQuery += "BEGIN:VFREEBUSY\n";
  1623.         fbQuery += "DTSTAMP:" + dtstamp + "\n";
  1624.         fbQuery += "ORGANIZER:" + organizer + "\n";
  1625.         fbQuery += "DTSTART:" + dtstart + "\n";
  1626.         fbQuery += "DTEND:" + dtend + "\n";
  1627.         fbQuery += "UID:" + uuid + "\n";
  1628.         var attendee = "ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;CN=" + mailto_aCalId + "\n";
  1629.         var attendeeFolded = this.foldLine(attendee);
  1630.         fbQuery += attendeeFolded;
  1631.         fbQuery += "END:VFREEBUSY\n";
  1632.         fbQuery += "END:VCALENDAR\n";
  1633.         // RFC 2445 is specific about how lines end...
  1634.         fbQuery = fbQuery.replace(/\n/g, "\r\n");
  1635.  
  1636.         var httpchannel = this.prepChannel(outBoxUri, fbQuery,
  1637.                                            "text/calendar; charset=utf-8");
  1638.         httpchannel.requestMethod = "POST";
  1639.         httpchannel.setRequestHeader("Originator", organizer, false);
  1640.         httpchannel.setRequestHeader("Recipient", mailto_aCalId, false);
  1641.  
  1642.         var streamListener = {};
  1643.  
  1644.         streamListener.onStreamComplete =
  1645.             function caldav_GFBI_oSC(aLoader, aContext, aStatus,
  1646.                                          aResultLength, aResult) {
  1647.             var resultConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
  1648.                                              .createInstance(Components
  1649.                                              .interfaces.nsIScriptableUnicodeConverter);
  1650.  
  1651.             resultConverter.charset = "UTF-8";
  1652.             var str;
  1653.             try {
  1654.                 str = resultConverter.convertFromByteArray(aResult, aResultLength);
  1655.             } catch(e) {
  1656.                 LOG("Failed to parse freebusy response");
  1657.             }
  1658.  
  1659.             if (aContext.responseStatus == 200) {  // XXX = better error handling
  1660.                 var periodsToReturn = [];
  1661.                 var CalPeriod = new Components.
  1662.                                     Constructor("@mozilla.org/calendar/period;1",
  1663.                                                "calIPeriod");
  1664.                 var CalDateTime = new Components.
  1665.                                       Constructor("@mozilla.org/calendar/datetime;1",
  1666.                                                 "calIDateTime");
  1667.                 var fbTypeMap = {};
  1668.                 fbTypeMap["FREE"] = calIFreeBusyInterval.FREE;
  1669.                 fbTypeMap["BUSY"] = calIFreeBusyInterval.BUSY;
  1670.                 fbTypeMap["BUSY-UNAVAILABLE"] = calIFreeBusyInterval.BUSY_UNAVAILABLE;
  1671.                 fbTypeMap["BUSY-TENTATIVE"] = calIFreeBusyInterval.BUSY_TENTATIVE;
  1672.                 var C = new Namespace("C", "urn:ietf:params:xml:ns:caldav");
  1673.                 var D = new Namespace("D", "DAV:");
  1674.  
  1675.                 if (str.substr(0,6) == "<?xml ") {
  1676.                     str = str.substring(str.indexOf('\n'));
  1677.                 }
  1678.                 str = str.replace(/\n\ /g, "");
  1679.                 str = str.replace(/\r/g, "");
  1680.  
  1681.                 var response = new XML(str);
  1682.                 var status = response..C::response..C::["request-status"];
  1683.                 if (status.substr(0,1) != 2) {
  1684.                     LOG("Got status " + status + " in response to freebusy query");
  1685.                     return;
  1686.                 }
  1687.                 if (status.substr(0,3) != "2.0") {
  1688.                     LOG("Got status " + status + " in response to freebusy query");
  1689.                 }
  1690.                 var caldata = response..C::response..C::["calendar-data"];
  1691.                 var lines = caldata.split("\n");
  1692.                 for (var i = 0; i < lines.length; i++) {
  1693.                     if (lines[i].substr(0,8) == "FREEBUSY") {
  1694.                         var descDat = lines[i].split(":");
  1695.                         var fbName = descDat[0].split("=")[1];
  1696.                         var fbType = fbTypeMap[fbName];
  1697.                         var ranges = descDat[1].split(",");
  1698.                         for (var j = 0; j < ranges.length; j++) {
  1699.                             var parts = ranges[j].split("/");
  1700.                             var begin = new CalDateTime();
  1701.                             begin.icalString = parts[0];
  1702.                             var end = new CalDateTime();
  1703.                             end.icalString = parts[1];
  1704.                             var period = new CalPeriod();
  1705.                             period.start = begin;
  1706.                             period.end = end;
  1707.                             period.makeImmutable();
  1708.                             var interval = {
  1709.                                 QueryInterface: function fbInterval_QueryInterface(iid) {
  1710.                                     ensureIID([calIFreeBusyInterval, nsISupports], iid);
  1711.                                     return this;
  1712.                                 },
  1713.                                 calId: aCalId,
  1714.                                 interval: period,
  1715.                                 freeBusyType: fbType
  1716.                             };
  1717.                             periodsToReturn.push(interval);
  1718.                         }
  1719.                     }
  1720.                 }
  1721.                 aListener.onResult(null, periodsToReturn);
  1722.             } else {
  1723.                 LOG("Received status " + aContext.responseStatus + " from freebusy query");
  1724.             }
  1725.  
  1726.         }
  1727.  
  1728.         var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
  1729.                              .createInstance(Components.interfaces
  1730.                              .nsIStreamLoader);
  1731.  
  1732.         if (isOnBranch) {
  1733.             streamLoader.init(httpchannel, streamListener, httpchannel);
  1734.         } else {
  1735.             streamLoader.init(streamListener);
  1736.             httpchannel.asyncOpen(streamLoader, httpchannel);
  1737.         }
  1738.  
  1739.     },
  1740.  
  1741.     /**
  1742.      * RFC 2445 line folding
  1743.      */
  1744.     foldLine: function caldav_foldLine(aString) {
  1745.         var parts = [];
  1746.         while (aString.length) {
  1747.             var part = aString.substr(0,72);
  1748.             parts.push(part);
  1749.             aString = aString.substr(72);
  1750.         }
  1751.         return parts.join("\n ");
  1752.     },
  1753.  
  1754.     ensurePath: function caldav_ensurePath(aString) {
  1755.         if (aString.charAt(0) != "/") {
  1756.             var bogusUri = makeURL(aString);
  1757.             return bogusUri.path;
  1758.         }
  1759.         return aString;
  1760.     },
  1761.  
  1762.     // stubs to keep callbacks we don't support yet from throwing errors
  1763.     // we don't care about
  1764.     // nsIProgressEventSink
  1765.     onProgress: function onProgress(aRequest, aContext, aProgress, aProgressMax) {},
  1766.     onStatus: function onStatus(aRequest, aContext, aStatus, aStatusArg) {},
  1767.     // nsIDocShellTreeItem
  1768.     findItemWithName: function findItemWithName(name, aRequestor, aOriginalRequestor) {}
  1769. };
  1770.  
  1771. function WebDavResource(url) {
  1772.     this.mResourceURL = url;
  1773. }
  1774.  
  1775. WebDavResource.prototype = {
  1776.     mResourceURL: {},
  1777.     get resourceURL() {
  1778.         return this.mResourceURL;}  ,
  1779.     QueryInterface: function(iid) {
  1780.         if (iid.equals(CI.nsIWebDAVResource) ||
  1781.             iid.equals(CI.nsISupports)) {
  1782.             return this;
  1783.         }
  1784.  
  1785.         throw Components.interfaces.NS_ERROR_NO_INTERFACE;
  1786.     }
  1787. };
  1788.  
  1789. function WebDavListener() {
  1790. }
  1791.  
  1792. WebDavListener.prototype = {
  1793.  
  1794.     QueryInterface: function (aIID) {
  1795.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  1796.             !aIID.equals(nsIWebDavOperationListener)) {
  1797.             throw Components.results.NS_ERROR_NO_INTERFACE;
  1798.         }
  1799.  
  1800.         return this;
  1801.     },
  1802.  
  1803.     onOperationComplete: function(aStatusCode, aResource, aOperation,
  1804.                                   aClosure) {
  1805.         // aClosure is the listener
  1806.         aClosure.onOperationComplete(this, aStatusCode, 0, null, null);
  1807.  
  1808.         LOG("WebDavListener.onOperationComplete() called");
  1809.         return;
  1810.     },
  1811.  
  1812.     onOperationDetail: function(aStatusCode, aResource, aOperation, aDetail,
  1813.                                 aClosure) {
  1814.         LOG("WebDavListener.onOperationDetail() called");
  1815.         return;
  1816.     }
  1817. }
  1818.  
  1819. function calDavObserver(aCalendar) {
  1820.     this.mCalendar = aCalendar;
  1821. }
  1822.  
  1823. calDavObserver.prototype = {
  1824.     mCalendar: null,
  1825.     mInBatch: false,
  1826.  
  1827.     // calIObserver:
  1828.     onStartBatch: function() {
  1829.         this.mCalendar.observers.notify("onStartBatch");
  1830.         this.mInBatch = true;
  1831.     },
  1832.     onEndBatch: function() {
  1833.         this.mCalendar.observers.notify("onEndBatch");
  1834.         this.mInBatch = false;
  1835.     },
  1836.     onLoad: function(calendar) {
  1837.         this.mCalendar.observers.notify("onLoad", [calendar]);
  1838.     },
  1839.     onAddItem: function(aItem) {
  1840.         this.mCalendar.observers.notify("onAddItem", [aItem]);
  1841.     },
  1842.     onModifyItem: function(aNewItem, aOldItem) {
  1843.         this.mCalendar.observers.notify("onModifyItem", [aNewItem, aOldItem]);
  1844.     },
  1845.     onDeleteItem: function(aDeletedItem) {
  1846.         this.mCalendar.observers.notify("onDeleteItem", [aDeletedItem]);
  1847.     },
  1848.     onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
  1849.         this.mCalendar.observers.notify("onPropertyChanged", [aCalendar, aName, aValue, aOldValue]);
  1850.     },
  1851.     onPropertyDeleting: function(aCalendar, aName) {
  1852.         this.mCalendar.observers.notify("onPropertyDeleting", [aCalendar, aName]);
  1853.     },
  1854.  
  1855.     // Unless an error number is in this array, we consider it very bad, set
  1856.     // the calendar to readOnly, and give up.
  1857.     acceptableErrorNums: [],
  1858.  
  1859.     onError: function(aErrNo, aMessage) {
  1860.         var errorIsOk = false;
  1861.         for each (num in this.acceptableErrorNums) {
  1862.             if (num == aErrNo) {
  1863.                 errorIsOk = true;
  1864.                 break;
  1865.             }
  1866.         }
  1867.         if (!errorIsOk)
  1868.             this.mCalendar.readOnly = true;
  1869.         this.mCalendar.observers.notify("onError", [aErrNo, aMessage]);
  1870.     }
  1871. };
  1872.  
  1873. var g_fbService = null;
  1874. function getFreeBusyService() {
  1875.     if (!g_fbService) {
  1876.         g_fbService =
  1877.             Components.classes["@mozilla.org/calendar/freebusy-service;1"]
  1878.                       .getService(Components.interfaces.calIFreeBusyService);
  1879.     }
  1880.     return g_fbService;
  1881. };
  1882.